package com.adwyxx.collection;

import java.util.*;
import java.util.concurrent.CountDownLatch;

/**
 * {@link Set}接口
 * <pre>
 * - Set接口是Collection的子接口，Set接口相较于Collection接口没有提供额外的方法
 * - Set 集合不允许包含相同的元素，如果试把两个相同的元素加入同一个 Set 集合中，则添加操作失败。
 * - Set 集合支持的遍历方式和Collection集合一样：foreach 和 Iterator。
 * - Set 的常用实现类有：HashSet、TreeSet、LinkedHashSet
 * </pre>
 * Set接口的继承关系：
 *<pre>
 * [Iterable<T>]
 *       ↓
 * [Collection<E>]
 *       ↓       |-> [SortedSet] ->|-> [TreeSet]  // 有序的
 *    [Set<E>] ->|-> [AbstractSet] |-> [HashSet]  // 无序的
 *                                         ↓
 *                                  [LinkedHashSet] // 有序的
 *
 * </pre>
 *
 * @author Leo.W
 * @date 2024-09-14 13:42
 */
public class SetDemo implements Comparable<SetDemo> {

    private String name;
    private int index;
    public SetDemo(String name,int index){
        this.name = name;
        this.index = index;
    }

    /**
     * 重写 equals(Object o) 方法，用于判断 Collection 集合中的元素是否相等
     * @param o
     * @return If this and o are the same HashCode, then return true.
     */
    @Override
    public boolean equals(Object o){
       return this.hashCode() == o.hashCode();
    }

    /**
     * 重写 hashCode() 方法，用于计算对象在哈希表中的存储位置
     * @return Hash Code
     */
    @Override
    public int hashCode(){
        return this.toString().hashCode();
    }

    /**
     * 重写 toString() 方法
     * @return
     */
    @Override
    public String toString(){
        return "{name:"+this.name+",index:"+this.index+"}";
    }

    /**
     * 实现 Comparable 接口：TreeSet 要求存储的元素必须实现 Comparable 接口，以便在添加元素时进行排序。
     * 对于 TreeSet 集合而言，它判断两个对象是否相等的唯一标准是：两个对象通过 compareTo(Object obj) 或 compare(Object o1,Object o2) 方法比较返回值。
     * 如果方法返回正整数，则表示 this 大于 o；
     * 如果返回0，表示相等；
     * 返回负整数，表示 this 小于 o
     *
     * @param o the object to be compared.
     * @return 返回负整数、零或正整数，对应于当前对象 < o,== o, > o.
     * @throws NullPointerException if the specified object is null
     * @throws ClassCastException   if the specified object's type prevents it
     *                              from being compared to this object.
     */
    @Override
    public int compareTo(SetDemo o) {
        return this.index - o.index;
    }

    public static void main(String[] args) {
        /**
         * HashSet 是 Set 接口的一个实现类，HashSet 使用哈希表（实际上是一个 HashMap 实例）来存储集合中的元素，因此 HashSet 具有以下特点：
         * <pre>
         *  - 不允许存储重复元素: 通过 equals() 和 hashCode() 方法判断两个元素是否相等，如果相等则不允许添加到集合中。
         *  - 没有索引，不能使用下标操作
         *  - 是一个无序集合，存储元素顺序和添加顺序不一致
         *  - 不是线程安全的
         * </pre>
         */
        Set<SetDemo> set = new HashSet<>();
        set.add(new SetDemo("A",1));
        set.add(new SetDemo("B",2));
        set.add(new SetDemo("C",3));
        System.out.println(set);
        // [{name:A,index:1}, {name:C,index:3}, {name:B,index:2}]
        // 由上输出可以看出Set的无序性：不等同于随机性。这里的无序性与元素的添加位置有关。
        // 具体来说：我们在添加每一个元素到数组中时，具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的，表现出一定的无序性。

        // 使用 contains(Object o) 方法判断集合中是否包含指定元素，由于重写了 equals() 和 hashCode() 方法，即使不同的对象也可根据内容判断出相等。
        System.out.println(set.contains(new SetDemo("A",1))); // true

        // 使用 Iterator 遍历
        Iterator it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        // 使用 foreach 遍历
        for(SetDemo item : set){
            System.out.println(item);
        }
        // 使用 lambda forEach 表达式遍历
        set.forEach(item -> System.out.println(item));
        // 使用 Stream API 遍历
        set.stream().forEach(item -> System.out.println(item));

        /**
         * TreeSet 是 Set 接口的一个实现类，TreeSet 使用红黑树（一种自平衡二叉搜索树）来存储集合中的元素，因此 TreeSet 具有以下特点：
         * <pre>
         * - 不允许存储重复元素: 通过 compareTo() 方法判断两个元素是否相等，如果相等则不允许添加到集合中。
         * - 没有索引，不能使用下标操作
         * - 是一个有序集合，存储元素顺序和添加顺序不一致
         * - 不是线程安全的
         * </pre>
         * TreeSet 两种排序方法：`自然排序`和`定制排序`
         * <pre>
         * 自然排序：TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系(对象的类必须实现 Comparable<T> 接口的 compareTo(T obj) 方法)，然后将集合元素按升序(默认情况)排列
         * 定制排序：通过 Comparator 接口的实现类来实现排序。需要重写compare(T o1,T o2)方法。TreeSet 的元素可以实现 Comparable 接口
         * </pre>
         */
        System.out.println("---------- TreeSet ----------");
        // 自然排序
        Set<SetDemo> treeSet = new TreeSet<>();
        treeSet.add(new SetDemo("A",1));
        treeSet.add(new SetDemo("B",2));
        treeSet.add(new SetDemo("C",3));
        System.out.println(treeSet); // [{name:A,index:1}, {name:B,index:2}, {name:C,index:3}]
        // 定制排序
        Set<Integer> treeSet2 = new TreeSet<>(new MyComparator());
        treeSet2.add(1);
        treeSet2.add(3);
        treeSet2.add(2);
        System.out.println(treeSet2); // [1, 2, 3]

        /**
         * LinkedHashSet: 是 HashSet 的子类，它使用链表维护元素的顺序，因此它是有序的。LinkedHashSet 具有以下特点：
         * <pre>
         * - LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置，但它同时使用双向链表维护元素的次序，这使得元素看起来是以添加顺序保存的。
         * - LinkedHashSet插入性能略低于 HashSet，但在迭代访问 Set 里的全部元素时有很好的性能
         * - 是一个有序集合，存储元素顺序和添加顺序一致
         * - 不是线程安全的
         * </pre>
         */
        System.out.println("---------- LinkedHashSet ----------");
        Set<Integer> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add(1);
        linkedHashSet.add(3);
        linkedHashSet.add(2);
        System.out.println(linkedHashSet); // [1, 3, 2]

        /**
         * HashSet/LinkedHashSet/TreeSet 本身不是线程安全的。如果在多线程环境下使用 Collections.synchronizedSortedSet() 方法来创建一个线程安全的 HashSet/LinkedHashSet/TreeSet。
         */
        System.out.println("---------- Collections.synchronizedSortedSet ----------");
        CountDownLatch latch = new CountDownLatch(5);
        Set<Integer> syncSet = Collections.synchronizedSortedSet(new TreeSet<>(new MyComparator()));
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                for (int j = 0; j < 20; j++){
                   if(syncSet.add(j)) {
                        System.out.println(Thread.currentThread().getName() + " added " + j);
                    }
                }
                latch.countDown();
            },"thread-"+i).start();
        }
        try {
            latch.await();
            System.out.println(syncSet);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 定制排序：通过 Comparator 接口的实现类来实现排序。需要重写compare(T o1,T o2)方法。
     */
    static class MyComparator implements Comparator<Integer> {
        /**
         * 比较两个对象的大小
         * @param o1 对象1
         * @param o2 对象2
         * @return 正整数则表示 o1 大于 o2；0表示相等；负整数表示 o1 小于 o2
         */
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    }

}
